package sample.classremaper;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.ClassRemapper;
import org.objectweb.asm.commons.SimpleRemapper;
import org.objectweb.asm.tree.ClassNode;
import util.CustomClassLoader;
import util.FileUtils;

import java.io.IOException;

public class ClassMergeDemo {
    public static void main(String[] args) throws IOException {
        byte[] bytes = mergeClass(HelloWorld.class, GoodChild.class);
        //  output
        FileUtils.writeBytes(Type.getInternalName(HelloWorld.class) + "MergeChild.class", bytes);
        //  replace
        FileUtils.writeBytes(HelloWorld.class.getResource("/").getPath() + Type.getInternalName(HelloWorld.class) + ".class", bytes);
        invokeMethod(bytes);
    }

    /**
     * TODO: 2021/11/11 静态 final的字段，不能拆分到static方法，会报错！
     */
    private static byte[] mergeClass(Class<?> clazzA, Class<?> clazzB) throws IOException {
        ClassReader classReader = new ClassReader(clazzA.getName());
        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
        ClassNode anotherClassNode = renameClassNode(clazzB, Type.getInternalName(clazzA));
        ClassMergerVisitor visitor = new ClassMergerVisitor(Opcodes.ASM9, "class_init$", classWriter, anotherClassNode);
        classReader.accept(visitor, ClassReader.EXPAND_FRAMES);
        return classWriter.toByteArray();
    }

    private static ClassNode renameClassNode(Class<?> clazz, String newName) throws IOException {
        ClassReader classReader = new ClassReader(clazz.getName());
        ClassNode classNode = new ClassNode();
        SimpleRemapper simpleRemapper = new SimpleRemapper(Type.getInternalName(clazz), newName);
        ClassRemapper visitor = new ClassRemapper(classNode, simpleRemapper);
        classReader.accept(visitor, ClassReader.EXPAND_FRAMES);
        return classNode;
    }

    private static void invokeMethod(byte[] bytes) {
        CustomClassLoader classLoader = new CustomClassLoader();
        Class clazz = classLoader.defineClass(HelloWorld.class.getName(), bytes);
        Object instance = classLoader.invokeInstance(clazz, new Class[]{}, new Object[]{});
        classLoader.invokeMethod(clazz, instance, new Class[]{}, "test", new Object[]{});
        classLoader.invokeMethod(clazz, instance, new Class[]{}, "printDate", new Object[]{});
    }
}
